<script>
import Loading from '@shell/components/Loading';
import { Banner } from '@components/Banner';
import CreateEditView from '@shell/mixins/create-edit-view';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import KeyValue from '@shell/components/form/KeyValue';
import UnitInput from '@shell/components/form/UnitInput';
import { RadioGroup } from '@components/Form/Radio';
import { Checkbox } from '@components/Form/Checkbox';
import { NORMAN } from '@shell/config/types';
import { allHash } from '@shell/utils/promise';
import { addObject, addObjects, findBy } from '@shell/utils/array';
import { convertStringToKV, convertKVToString } from '@shell/utils/object';
import { sortBy } from '@shell/utils/sort';
import { stringify, exceptionToErrorsArray } from '@shell/utils/error';

const DEFAULT_GROUP = 'rancher-nodes';

export default {
  components: {
    Banner, Loading, LabeledInput, LabeledSelect, Checkbox, RadioGroup, UnitInput, KeyValue
  },

  mixins: [CreateEditView],

  props: {
    uuid: {
      type:     String,
      required: true,
    },

    cluster: {
      type:    Object,
      default: () => ({})
    },

    credentialId: {
      type:     String,
      required: true,
    },

    disabled: {
      type:    Boolean,
      default: false
    },
  },

  async fetch() {
    this.errors = [];
    if ( !this.credentialId ) {
      return;
    }

    try {
      if ( this.credential?.id !== this.credentialId ) {
        this.credential = await this.$store.dispatch('rancher/find', { type: NORMAN.CLOUD_CREDENTIAL, id: this.credentialId });
      }
    } catch (e) {
      this.credential = null;
    }

    try {
      const region = this.value.region || this.credential?.decodedData.defaultRegion || this.$store.getters['aws/defaultRegion'];

      if ( !this.value.region ) {
        this.value['region'] = region;
      }

      this.ec2Client = await this.$store.dispatch('aws/ec2', { region, cloudCredentialId: this.credentialId });
      this.kmsClient = await this.$store.dispatch('aws/kms', { region, cloudCredentialId: this.credentialId });

      if ( !this.instanceInfo ) {
        this.instanceInfo = await this.$store.dispatch('aws/describeInstanceTypes', { client: this.ec2Client } );
      }

      const hash = {};

      if ( !this.regionInfo ) {
        hash.regionInfo = this.ec2Client.describeRegions({});
      }

      if ( this.loadedRegionalFor !== region ) {
        hash.zoneInfo = await this.ec2Client.describeAvailabilityZones({});
        hash.vpcInfo = await this.ec2Client.describeVpcs({});
        hash.subnetInfo = await this.ec2Client.describeSubnets({});
        hash.securityGroupInfo = await this.ec2Client.describeSecurityGroups({});
      }

      const res = await allHash(hash);

      for ( const k in res ) {
        this[k] = res[k];
      }

      try {
        this.kmsInfo = await this.kmsClient.listKeys({});
        this.canReadKms = true;
      } catch (e) {
        this.canReadKms = false;
      }

      if ( !this.value.zone ) {
        this.value['zone'] = 'a';
      }

      if ( !this.value.instanceType ) {
        this.value['instanceType'] = this.$store.getters['aws/defaultInstanceType'];
      }

      this.initNetwork();
      this.initTags();

      if ( !this.value.securityGroup?.length ) {
        this.value['securityGroup'] = [DEFAULT_GROUP];
      }

      if ( this.value.securityGroup?.length === 1 && this.value.securityGroup[0] === DEFAULT_GROUP ) {
        this.securityGroupMode = 'default';
      } else {
        this.securityGroupMode = 'custom';
      }

      this.loadedRegionalFor = region;
    } catch (e) {
      this.errors = exceptionToErrorsArray(e);
    }
  },

  data() {
    return {
      ec2Client:         null,
      kmsClient:         null,
      credential:        null,
      instanceInfo:      null,
      regionInfo:        null,
      canReadKms:        null,
      kmsInfo:           null,
      tags:              null,
      loadedRegionalFor: null,
      zoneInfo:          null,
      vpcInfo:           null,
      subnetInfo:        null,
      securityGroupInfo: null,
      selectedNetwork:   null,
      securityGroupMode: null,
    };
  },

  computed: {
    securityGroupLabels() {
      return [
        this.t('cluster.machineConfig.amazonEc2.securityGroup.mode.default', { defaultGroup: DEFAULT_GROUP }),
        this.t('cluster.machineConfig.amazonEc2.securityGroup.mode.custom')
      ];
    },

    isIamInstanceProfileNameRequired() {
      return this.cluster?.cloudProvider === 'aws';
    },

    instanceOptions() {
      let lastGroup;

      const out = [];

      for ( const row of this.instanceInfo ) {
        if ( row.groupLabel !== lastGroup ) {
          out.push({
            kind:     'group',
            disabled: true,
            label:    row.groupLabel
          });

          lastGroup = row.groupLabel;
        }

        out.push({
          label: row['label'],
          value: row['apiName'],
        });
      }

      return out;
    },

    regionOptions() {
      if ( !this.regionInfo ) {
        return [];
      }

      return this.regionInfo.Regions.map((obj) => {
        return obj.RegionName;
      }).sort();
    },

    zoneOptions() {
      if ( !this.zoneInfo ) {
        return [];
      }

      return this.zoneInfo.AvailabilityZones.map((obj) => {
        return obj.ZoneName.substr(-1);
      }).sort();
    },

    networkOptions() {
      if ( !this.vpcInfo || !this.subnetInfo ) {
        return [];
      }

      let vpcs = [];
      const subnetsByVpc = {};

      for ( const obj of this.vpcInfo.Vpcs ) {
        const name = obj.Tags && obj.Tags?.length ? obj.Tags.find((t) => t.Key === 'Name')?.Value : null;

        vpcs.push({
          label:     name || obj.VpcId,
          subLabel:  name ? obj.VpcId : obj.CidrBlock,
          isDefault: obj.IsDefault || false,
          kind:      'vpc',
          value:     obj.VpcId,
        });
      }

      vpcs = sortBy(vpcs, ['isDefault:desc', 'label']);

      for ( const obj of this.subnetInfo.Subnets ) {
        if ( obj.AvailabilityZone !== `${ this.value.region }${ this.value.zone }` ) {
          continue;
        }

        let entry = subnetsByVpc[obj.VpcId];

        if ( !entry ) {
          entry = [];
          subnetsByVpc[obj.VpcId] = entry;
        }

        const name = obj.Tags && obj.Tags?.length ? obj.Tags.find((t) => t.Key === 'Name')?.Value : null;

        entry.push({
          label:     name || obj.SubnetId,
          subLabel:  name ? obj.SubnetId : obj.CidrBlock,
          kind:      'subnet',
          isDefault: obj.DefaultForAz || false,
          value:     obj.SubnetId,
          vpcId:     obj.VpcId,
        });
      }

      const out = [];

      for ( const obj of vpcs ) {
        addObject(out, obj);

        if ( subnetsByVpc[obj.value] ) {
          addObjects(out, sortBy(subnetsByVpc[obj.value], ['isDefault:desc', 'label']));
        }
      }

      return out;
    },

    securityGroupOptions() {
      if ( !this.securityGroupInfo ) {
        return [];
      }

      const out = this.securityGroupInfo.SecurityGroups.filter((obj) => {
        return obj.VpcId === this.value.vpcId;
      }).map((obj) => {
        return {
          label:       obj.GroupName,
          description: obj.GroupDescription,
          value:       obj.GroupName
        };
      });

      return sortBy(out, 'label');
    },

    kmsOptions() {
      if ( !this.kmsInfo ) {
        return [];
      }

      const out = this.kmsInfo.Keys.map((obj) => {
        return obj.KeyArn;
      }).sort();

      return out;
    },

    DEFAULT_GROUP() {
      return DEFAULT_GROUP;
    }
  },

  watch: {
    'credentialId'() {
      this.$fetch();
    },

    'value.region'() {
      this.updateNetwork();
      this.$fetch();
    },

    'value.zone'() {
      this.$fetch();
    },

    'securityGroupMode'(val) {
      this.value.securityGroupReadonly = ( val !== 'default' );
    },
  },

  methods: {
    stringify,

    initNetwork() {
      const id = this.value.subnetId || this.value.vpcId;

      this.selectedNetwork = id;
    },

    updateNetwork(value) {
      let obj;

      if ( value ) {
        obj = findBy(this.networkOptions, 'value', value);
      }

      if ( obj?.kind === 'subnet' ) {
        this.value.subnetId = value;
        this.value.vpcId = obj.vpcId;
        this.selectedNetwork = value;
      } else if ( obj ) {
        this.value.subnetId = null;
        this.value.vpcId = value;
        this.selectedNetwork = value;
      } else {
        this.value.subnetId = null;
        this.value.vpcId = null;
        this.selectedNetwork = null;
      }
    },

    initTags() {
      this.tags = convertStringToKV(this.value.tags);
    },

    updateTags(tags) {
      this.value['tags'] = convertKVToString(tags);
    },

    test() {
      const errors = [];

      if (!this.selectedNetwork) {
        errors.push(this.t('validation.required', { key: 'VPC/Subnet' }, true));
      }

      return { errors };
    },
  },
};
</script>

<template>
  <div>
    <Loading v-if="$fetchState.pending" />
    <template v-else>
      <div v-if="errors.length">
        <div
          v-for="(err, idx) in errors"
          :key="idx"
        >
          <Banner
            color="error"
            :label="stringify(err)"
          />
        </div>
      </div>

      <div v-if="loadedRegionalFor">
        <div class="row mb-20">
          <div class="col span-6">
            <LabeledSelect
              v-model:value="value.region"
              :mode="mode"
              :options="regionOptions"
              :required="true"
              :searchable="true"
              :disabled="disabled"
              :label="t('cluster.machineConfig.amazonEc2.region')"
            />
          </div>
          <div class="col span-6">
            <LabeledSelect
              v-model:value="value.zone"
              :mode="mode"
              :options="zoneOptions"
              :required="true"
              :disabled="disabled"
              :label="t('cluster.machineConfig.amazonEc2.zone')"
            />
          </div>
        </div>
        <div class="row mb-20">
          <div class="col span-9">
            <LabeledSelect
              v-model:value="value.instanceType"
              :mode="mode"
              :options="instanceOptions"
              :required="true"
              :selectable="option => !option.disabled"
              :searchable="true"
              :disabled="disabled"
              :label="t('cluster.machineConfig.amazonEc2.instanceType')"
            >
              <template v-slot:option="opt">
                <template v-if="opt.kind === 'group'">
                  <b>{{ opt.label }}</b>
                </template>
                <template v-else>
                  <span class="pl-10">{{ opt.label }}</span>
                </template>
              </template>
            </LabeledSelect>
          </div>
          <div class="col span-3">
            <UnitInput
              v-model:value="value.rootSize"
              output-as="string"
              :mode="mode"
              :disabled="disabled"
              :placeholder="t('cluster.machineConfig.amazonEc2.rootSize.placeholder')"
              :label="t('cluster.machineConfig.amazonEc2.rootSize.label')"
              :suffix="t('cluster.machineConfig.amazonEc2.rootSize.suffix')"
            />
          </div>
        </div>
        <div class="row mt-20 mb-20">
          <div class="col span-6">
            <LabeledSelect
              :mode="mode"
              :value="selectedNetwork"
              :options="networkOptions"
              :searchable="true"
              :required="true"
              :disabled="disabled"
              :placeholder="t('cluster.machineConfig.amazonEc2.selectedNetwork.placeholder')"
              :label="t('cluster.machineConfig.amazonEc2.selectedNetwork.label')"
              data-testid="amazonEc2__selectedNetwork"
              option-key="value"
              @update:value="updateNetwork($event)"
            >
              <template v-slot:option="opt">
                <div :class="{'vpc': opt.kind === 'vpc', 'vpc-subnet': opt.kind !== 'vpc'}">
                  <span class="vpc-name">{{ opt.label }}</span><span class="vpc-info">{{ opt.subLabel }}</span>
                </div>
              </template>
            </LabeledSelect>
          </div>
          <div class="col span-6">
            <LabeledInput
              v-model:value="value.iamInstanceProfile"
              :mode="mode"
              :disabled="disabled"
              :required="isIamInstanceProfileNameRequired"
              :tooltip="t('cluster.machineConfig.amazonEc2.iamInstanceProfile.tooltip')"
              :label="t('cluster.machineConfig.amazonEc2.iamInstanceProfile.label')"
            />
          </div>
        </div>

        <portal :to="'advanced-'+uuid">
          <div class="row mt-20">
            <div class="col span-6">
              <LabeledInput
                v-model:value="value.ami"
                :mode="mode"
                :disabled="disabled"
                :placeholder="t('cluster.machineConfig.amazonEc2.ami.placeholder')"
                :label="t('cluster.machineConfig.amazonEc2.ami.label')"
              />
            </div>
            <div class="col span-6">
              <LabeledInput
                v-model:value="value.sshUser"
                :mode="mode"
                :label="t('cluster.machineConfig.amazonEc2.sshUser.label')"
                :disabled="!value.ami || disabled"
                :tooltip="t('cluster.machineConfig.amazonEc2.sshUser.tooltip')"
                :placeholder="t('cluster.machineConfig.amazonEc2.sshUser.placeholder')"
              />
            </div>
          </div>

          <div class="row mt-20">
            <div class="col span-12">
              <h3>
                {{ t('cluster.machineConfig.amazonEc2.securityGroup.title') }}
                <span
                  v-if="!value.vpcId"
                  class="text-muted text-small"
                >
                  {{ t('cluster.machineConfig.amazonEc2.securityGroup.vpcId') }}
                </span>
              </h3>
              <RadioGroup
                v-model:value="securityGroupMode"
                name="securityGroupMode"
                :mode="mode"
                :disabled="!value.vpcId || disabled"
                :labels="securityGroupLabels"
                :options="['default','custom']"
              />
              <LabeledSelect
                v-if="value.vpcId && securityGroupMode === 'custom'"
                v-model:value="value.securityGroup"
                :mode="mode"
                :disabled="!value.vpcId || disabled"
                :options="securityGroupOptions"
                :searchable="true"
                :multiple="true"
                :taggable="true"
              />
            </div>
          </div>

          <div class="row mt-20">
            <div class="col span-6">
              <LabeledInput
                v-model:value="value.volumeType"
                :mode="mode"
                :disabled="disabled"
                :label="t('cluster.machineConfig.amazonEc2.volumeType.label')"
                :placeholder="t('cluster.machineConfig.amazonEc2.volumeType.placeholder')"
              />
            </div>
          </div>

          <div class="row mt-20">
            <div class="col span-12">
              <Checkbox
                v-model:value="value.encryptEbsVolume"
                :mode="mode"
                :label="t('cluster.machineConfig.amazonEc2.encryptEbsVolume')"
              />
              <div
                v-if="value.encryptEbsVolume"
                class="mt-10"
              >
                <LabeledSelect
                  v-if="canReadKms"
                  v-model:value="value.kmsKey"
                  :mode="mode"
                  :options="kmsOptions"
                  :disabled="disabled"
                  :label="t('cluster.machineConfig.amazonEc2.kmsKey.label')"
                />
                <template v-else>
                  <LabeledInput
                    v-model:value="value.kmsKey"
                    :mode="mode"
                    :disabled="disabled"
                    :label="t('cluster.machineConfig.amazonEc2.kmsKey.label')"
                  />
                  <p class="text-muted">
                    {{ t('cluster.machineConfig.amazonEc2.kmsKey.text') }}
                  </p>
                </template>
              </div>
            </div>
          </div>
          <div class="row mt-20">
            <div class="col span-6">
              <Checkbox
                v-model:value="value.requestSpotInstance"
                :mode="mode"
                :label="t('cluster.machineConfig.amazonEc2.requestSpotInstance')"
              />
              <div
                v-if="value.requestSpotInstance"
                class="mt-10"
              >
                <UnitInput
                  v-model:value="value.spotPrice"
                  output-as="string"
                  :mode="mode"
                  :disabled="disabled"
                  :placeholder="t('cluster.machineConfig.amazonEc2.spotPrice.placeholder')"
                  :label="t('cluster.machineConfig.amazonEc2.spotPrice.label')"
                  :suffix="t('cluster.machineConfig.amazonEc2.spotPrice.suffix')"
                />
              </div>
            </div>
          </div>

          <div class="row mt-20">
            <div class="col span-12">
              <div>
                <Checkbox
                  v-model:value="value.privateAddressOnly"
                  :mode="mode"
                  :disabled="disabled"
                  :label="t('cluster.machineConfig.amazonEc2.privateAddressOnly')"
                />
              </div>
              <div>
                <Checkbox
                  v-model:value="value.useEbsOptimizedInstance"
                  :mode="mode"
                  :disabled="disabled"
                  :label="t('cluster.machineConfig.amazonEc2.useEbsOptimizedInstance')"
                />
              </div>
              <div>
                <Checkbox
                  v-model:value="value.httpEndpoint"
                  value-when-true="enabled"
                  :mode="mode"
                  :disabled="disabled"
                  :label="t('cluster.machineConfig.amazonEc2.httpEndpoint')"
                />
              </div>
              <div>
                <Checkbox
                  v-model:value="value.httpTokens"
                  value-when-true="required"
                  :mode="mode"
                  :disabled="!value.httpEndpoint || disabled"
                  :label="t('cluster.machineConfig.amazonEc2.httpTokens')"
                />
              </div>
            </div>
          </div>

          <div class="row mt-20">
            <div class="col span-12">
              <KeyValue
                :value="tags"
                :mode="mode"
                :read-allowed="false"
                :label="t('cluster.machineConfig.amazonEc2.tagTitle')"
                :add-label="t('labels.addTag')"
                :disabled="disabled"
                @update:value="updateTags"
              />
            </div>
          </div>
        </portal>
      </div>
    </template>
  </div>
</template>
<style scoped lang="scss">
  .vpc, .vpc-subnet {
    display: flex;
    line-height: 30px;

    .vpc-name {
      font-weight: bold;
      flex: 1;
    }

    .vpc-info {
      font-size: 12px;
      opacity: 0.7;
    }
  }

  .vpc-subnet .vpc-name {
    font-weight: normal;
    padding-left: 15px;
  }
</style>
